/*
 * Copyright (c) 2002 David Corbin and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 * 	  David Corbin  - initial implementation - currently delegates to PerforceUtil
 * 					- add moveFile method and several supporting routines
 *    Boris Pruessmann - Changes to determineFolderPosition, improved moveFile,
 *                       cleanup
 */
package net.sourceforge.p4eclipse.core.api;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import net.sourceforge.p4eclipse.core.PerforceException;
import net.sourceforge.p4eclipse.core.PerforceRepositoryProvider;
import net.sourceforge.p4eclipse.core.resources.LocalFile;
import net.sourceforge.p4eclipse.core.resources.LocalResource;
import net.sourceforge.p4eclipse.core.syncinfo.FolderSyncInfo;
import net.sourceforge.p4eclipse.core.syncinfo.ResourceSyncInfo;
import net.sourceforge.p4eclipse.ui.PerforceUIPlugin;
import net.sourceforge.p4eclipse.ui.views.PendingChangelistView;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.team.IResourceTree;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;

/**
  * @version 	$Revision: 23 $
  * @author		<a href="mailto:dcorbin@users.sourceforge.net">David Corbin</a>
  * @author 	<a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
public class ActualPerforceServer implements PerforceServer
{
	// ------------------------------------------------------------ Constructors

	/** Only PerforceServerFactory may create those ... */
	ActualPerforceServer()
	{
	}

	// ---------------------------------------------------------- Public Methods

	/** @see PerforceServer#determineFolderPosition(String) */
	public FolderRepositoryRelation determineFolderPosition(String folder)
	{
		try
		{
			if (folder.endsWith("/"))
				folder = folder.substring(0, folder.length() - 1);
			IPath folderPath = new Path(folder);

			IPath clientRootPath = getClientRootPath();
			if (clientRootPath.segmentCount() > 0)
			{
				if (!getClientRootPath().isPrefixOf(folderPath))
					return FolderRepositoryRelation.OUTSIDE_ROOT;
			}

			if (getFolderSyncInfo(folder).length > 0)
				return FolderRepositoryRelation.IN_REPOSITORY;

			if (getDepotFileName(folder) != null)
				return FolderRepositoryRelation.IN_VIEW;

			return FolderRepositoryRelation.UNDER_ROOT;
		}
		catch (PerforceException pe)
		{
			PerforceUIPlugin.getPlugin().log(pe.getStatus());
			return null;
		}
	}

	/** @see PerforceServer#getClient() */
	public ClientForm getClient()
	{
		return PerforceUtil.getClient();
	}



	public boolean moveFile(IResourceTree tree, IFile sourceFile, IFile destinationFile, IProgressMonitor monitor)
	{
		try
		{			
			refreshState(new IFile[] { sourceFile, destinationFile }, monitor);			
			
			LocalFile source = 
				(LocalFile)LocalResource.createFromResource(sourceFile);
			LocalFile destination = 
				(LocalFile)LocalResource.createFromResource(destinationFile);

			ResourceSyncInfo syncInfo = source.getSyncInfo();
			if (syncInfo.getAction().equals(ResourceSyncInfo.ACTION_ADD))
			{
				String resolved =
					PerforceUIPlugin.getClient().resolved(
						syncInfo.getClientFileName());
				
				// The file has already been integrated		
				if (resolved != null)
				{
					String originalFile =
						resolved.substring(0, resolved.indexOf('#'));
					originalFile = getLocalFileName(originalFile);
					
					String otherFile = 
						getLocalFileName(destination.getAbsolutePath());
					
					// The new destination was the original source
					if (originalFile.equals(otherFile))
					{
						return 
							moveToOriginalFile(tree, source, destination, monitor);
					}
					else
					{
						return 
							moveResolvedFile(tree, source, destination, resolved, monitor);
					}
				}
				else
				{
					return 
						moveNewlyAddedFile(tree, source, destination, monitor);
				}
			}

			return 
				moveUnopenedFile(tree, source, destination, monitor);
		}
		catch (PerforceException e)
		{
			PerforceUIPlugin.log(e.getStatus());
			
			try
			{
				revert((LocalFile)LocalFile.createFromResource(destinationFile));
			
				File file = new File(destinationFile.getLocation().toOSString());
				if (file.exists())
				{
					file.delete();
				}
			}
			catch (PerforceException ee)
			{
				PerforceUIPlugin.log(e.getStatus());
			}
			throw new RuntimeException();			
		}
		finally
		{
			refreshState(new IFile[] { sourceFile, destinationFile }, monitor);
			PendingChangelistView.getInstance().refreshClientChangelists();
		}
	}

	// --------------------------------------------------------- Private Methods

	/** @return the perforce IClient instance to use. */
	private IClient getP4Client() 
	{ 
		return PerforceUIPlugin.getClient(); 
	}
	
	/** Returns the FolderSyncInfo for the specified folder. */
	private FolderSyncInfo[] getFolderSyncInfo(String folder)
	{
		FolderSyncInfo[] result = new FolderSyncInfo[0];
		try
		{
			result = getP4Client().dirs(folder);
		}
		catch (PerforceException e)
		{
			PerforceUIPlugin.log(e.getStatus())	;
		}
		
		return result;
	}

	/** Retrieves the depot pathname of the specified path. */
	private String getDepotFileName(String path) 
		throws PerforceException
	{
		IWhereResult whereResult = getP4Client().where(path);
		return whereResult.getDepotFilename();
	}
	
	/** Retrieves the local filename of the specified path. */
	private String getLocalFileName(String path)
		throws PerforceException
	{
		IWhereResult whereResult = getP4Client().where(path);
		return whereResult.getLocalFilename();		
	}

	/** Returns the client's root Path. */
	private IPath getClientRootPath()
	{
		IInfoResult infoResult = getP4Client().info();
		String clientRoot = infoResult.getClientRoot();
		if (clientRoot.equalsIgnoreCase("null"))
			clientRoot = "";
		return new Path(clientRoot);
	}

	private String[] fileNames(LocalFile file)
	{
		return new String[] { file.getAbsolutePath() };
	}	

	/** @see IClient#revert(String[]) */
	private void revert(LocalFile[] files)
		throws PerforceException
	{
		getP4Client().revert(LocalFile.fileNames(files));
	}

	/** @see IClient#revert(String[]) */
	private void revert(LocalFile file)
		throws PerforceException
	{
		revert(new LocalFile[] { file });
	}

	/** @see IClient#add(String[]) */
	private void add(LocalFile[] files)
		throws PerforceException
	{
		getP4Client().add(LocalFile.fileNames(files));
	}

	/** @see IClient#add(String[]) */
	private void add(LocalFile file)
		throws PerforceException
	{
		add(new LocalFile[] { file });
	}

	/** @see IClient#edit(String[]) */
	private void edit(LocalFile[] files)
		throws PerforceException
	{
		getP4Client().edit(LocalFile.fileNames(files));
	}

	/** @see IClient#edit(String[]) */
	private void edit(LocalFile file)
		throws PerforceException
	{
		edit(new LocalFile[] { file });
	}

	/** @see IClient#delete(String[]) */
	private void delete(LocalFile file)
		throws PerforceException
	{
		getP4Client().delete(fileNames(file));
	}
	
	/** @see IClient#integrate(String, String) */	
	private void integrate(String from, String to)
		throws PerforceException
	{
		getP4Client().integrate(from,to);
	}

	/** @see PerforceRepositoryProvider#refreshState(
	  *       IResource[], int, IProgressMonitor) 
	  */
	private void refreshState(IFile[] files, IProgressMonitor monitor)
	{
		PerforceRepositoryProvider provider =
			(PerforceRepositoryProvider) RepositoryProvider.getProvider(
				files[0].getProject());
	
		try
		{		
			provider.refreshState(files, IResource.DEPTH_ZERO, monitor);
		}
		catch (TeamException e)
		{
			PerforceUIPlugin.log(e.getStatus());
		}
	}	

	/** Performs Perforce move for files that are not currently opened for .. */
	private boolean moveUnopenedFile(
		IResourceTree tree, 
		LocalFile source, 
		LocalFile destination,
		IProgressMonitor monitor)
		throws PerforceException 
	{
		integrate(
			source.getAbsolutePath(), destination.getAbsolutePath());
			
		if (source.isCheckedOut())
		{
			edit(destination);
			
			tree.standardMoveFile(
				(IFile)source.toResource(),
				(IFile)destination.toResource(),
				IResource.FORCE | IResource.KEEP_HISTORY,
				monitor);
		
			revert(source);
			delete(source);
		
			return true;				
		}
		return false;
	}

	/** Performs perforce move for a file that has been newly added to Perforce,
	  * but not via integrate.
	  */  
	private boolean moveNewlyAddedFile(
		IResourceTree tree, 
		LocalFile source, 
		LocalFile destination, 
		IProgressMonitor monitor)
		
		throws PerforceException 
	{
		tree.standardMoveFile(
			(IFile)source.toResource(),
			(IFile)destination.toResource(),
			IResource.FORCE | IResource.KEEP_HISTORY,
			monitor);

		add(destination);
		revert(source);		
		
		return true;			
	}

	/** Performs a move in which the destination is the file from which the
	  * original rename/integrate cycle started.
	  */
	private boolean moveToOriginalFile(
		IResourceTree tree,
		LocalFile source,
		LocalFile destination,
		IProgressMonitor monitor)
		throws PerforceException 
	{
		revert(destination);
		edit(destination);
		
		tree.standardMoveFile(
			(IFile)source.toResource(),
			(IFile)destination.toResource(),
			IResource.FORCE | IResource.KEEP_HISTORY,
			monitor);

		revert(source);
				 				
		return true;
	}

	/** Performs a Perforce move for a file that has already been integrated. */
	private boolean moveResolvedFile(
		IResourceTree tree,
		LocalFile source,
		LocalFile destination,
		String resolved,
		IProgressMonitor monitor)
		throws PerforceException 
	{
		integrate(resolved, destination.getAbsolutePath());
		edit(destination);

		tree.standardMoveFile(
			(IFile)source.toResource(),
			(IFile)destination.toResource(),
			IResource.FORCE | IResource.KEEP_HISTORY,
			monitor);

		revert(source);
		
		return true;
	}
}
